package com.gxar.quick.ar.server.base.facade;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.gxar.common.exception.APIRuntimeException;
import com.gxar.common.utils.AssertUtils;
import com.gxar.common.utils.CollectionUtils;
import com.gxar.common.utils.JacksonUtils;
import com.gxar.common.utils.StringUtils;
import com.gxar.quick.ar.server.base.common.constant.ArProtocolConstant;
import com.gxar.quick.ar.server.base.common.enums.protocol.ArDeviceLevelEnum;
import com.gxar.quick.ar.server.base.common.enums.protocol.ArProtocolEditorEnum;
import com.gxar.quick.ar.server.base.common.utils.AesUtils;
import com.gxar.quick.ar.server.base.common.utils.DeviceLevelUtils;
import com.gxar.quick.ar.server.base.factory.ClientArProtocolFactory;
import com.gxar.quick.ar.server.base.factory.EditorArProtocolFactory;
import com.gxar.quick.ar.server.base.model.ResultCodeEnum;
import com.gxar.quick.ar.server.base.model.protocol.ArVector3;
import com.gxar.quick.ar.server.base.model.protocol.CommonInputWorkInfo;
import com.gxar.quick.ar.server.base.model.protocol.client.ClientArProtocol;
import com.gxar.quick.ar.server.base.model.protocol.common.CommonArImageIdentifyModel;
import com.gxar.quick.ar.server.base.model.protocol.common.CommonArProtocol;
import com.gxar.quick.ar.server.base.model.protocol.editor.EditorArProtocol;
import com.gxar.quick.ar.server.base.model.protocol.vo.request.BaseQueryWorkRequest;
import com.gxar.quick.ar.server.base.model.works.vo.WorksItemPositionVO;
import com.gxar.quick.ar.server.base.model.works.vo.request.WorksUpdatePositionRequest;
import com.gxar.quick.ar.server.db.model.WorksEntity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Objects;
import java.util.Optional;

/**
 * 协议模型转换
 * <p>
 * 主要路径
 * <p>
 * EditorModel -> CommonModel -> ClientModel
 * <p>
 * 其他路径
 * <p>
 * ClientModel -> CommonModel -> EditorModel 次要路径, 未来可能出现客户端操作修改了位置、角度等元素
 * <p>
 * ClientModel -> CommonModel -> ClientModel 次要路径, 同上
 * <p>
 * EditorModel -> CommonModel -> EditorModel 路径不存在, 暂不支持编辑器互通
 * <p>
 *
 * @author zxh
 * @version 1.0
 * @created 2024/01/02.
 */
@Service
@Slf4j
public class ArProtocolConvertFacade {

    private static final String CONTENT_KEY_POSITION = "position";

    @Autowired
    private EditorArProtocolFactory editorArProtocolFactory;

    @Autowired
    private ClientArProtocolFactory clientArProtocolFactory;

    /**
     * 转换协议配置
     *
     * @param worksItem 作品信息
     * @param request   查询请求
     */
    public void convertProtocolConfiguration(CommonInputWorkInfo worksItem, BaseQueryWorkRequest request) {
        if (Objects.isNull(worksItem)) {
            log.info("无需进行协议转换");
            return;
        }
        if (StringUtils.isEmpty(worksItem.getIdentify()) || StringUtils.isEmpty(worksItem.getContent())) {
            log.info("无需进行协议转换, 配置内容为空");
            return;
        }

        ArProtocolEditorEnum sourceEditor = ArProtocolEditorEnum.getByName(worksItem.getEditor());
        if (Objects.isNull(sourceEditor)) {
            // 暂定默认来源都是Web编辑器构造
            sourceEditor = ArProtocolEditorEnum.WEB_EDITOR;
        }

        // 设备分层
        ArDeviceLevelEnum deviceLevel = DeviceLevelUtils.getDeviceLevel(request.getDevice());

        // 编辑器协议 => 通用协议
        EditorArProtocol editorArProtocol = editorArProtocolFactory.create(sourceEditor);
        editorArProtocol.fill(worksItem, request.getScene());
        CommonArProtocol commonArProtocol = editorArProtocol.convert2CommonProtocol();
        commonArProtocol.setWaterMark(worksItem.getWatermark());

        // 通用协议 => 客户端协议
        ClientArProtocol clientArProtocol = clientArProtocolFactory.create(request.getClient());
        clientArProtocol.convertFromCommonProtocol(commonArProtocol);

        // 更新配置信息
        String identifyConfiguration = clientArProtocol.getIdentifyConfiguration(deviceLevel);
        String contentConfiguration = clientArProtocol.getContentConfiguration(deviceLevel);
        if (ArProtocolConstant.USE_ENCRYPT_FOR_AR_CONFIG) {
            identifyConfiguration = AesUtils.encryptArConfig(identifyConfiguration);
            contentConfiguration = AesUtils.encryptArConfig(contentConfiguration);
        }
        worksItem.setIdentify(identifyConfiguration);
        worksItem.setContent(contentConfiguration);
    }

    public String updateWorkPositionByClient(WorksUpdatePositionRequest request, WorksEntity worksEntity) {
        if (Objects.isNull(request) || CollectionUtils.isEmpty(request.getPositions()) || StringUtils.isEmpty(worksEntity.getContent())) {
            log.info("无需更新作品信息");
            return worksEntity.getContent();
        }

        final ArProtocolEditorEnum editor = ArProtocolEditorEnum.getByNameOrDefault(worksEntity.getEditor(), ArProtocolEditorEnum.WEB_EDITOR);
        EditorArProtocol editorArProtocol = editorArProtocolFactory.create(editor);
        CommonInputWorkInfo commonWorkInfo = CommonInputWorkInfo.from(worksEntity);
        editorArProtocol.fill(commonWorkInfo, request.getScene());
        CommonArProtocol commonArProtocol = editorArProtocol.convert2CommonProtocol();

        // 直接读取JSON, 避免丢失数据
        JsonNode contents = JacksonUtils.readTree(worksEntity.getContent());
        if (contents.isEmpty()) {
            throw new APIRuntimeException(ResultCodeEnum.AR_SHOW_CONTENTS_IS_EMPTY);
        }

        if (ArProtocolEditorEnum.WEB_EDITOR.equals(editor)) {
            updateWebEditorPosition(request, contents, commonArProtocol);
        } else if (ArProtocolEditorEnum.UNITY_EDITOR.equals(editor)) {
            updateUnityEditorPosition(request, contents, commonArProtocol);
        } else {
            throw new APIRuntimeException(ResultCodeEnum.CLIENT_UPDATE_POSITION_FAILED, "未知的编辑器协议");
        }
        return JacksonUtils.toJsonString(contents);
    }

    private void updateWebEditorPosition(WorksUpdatePositionRequest request, JsonNode contents, CommonArProtocol commonArProtocol) {
        final Double spaceBaseSize = commonArProtocol.getSpaceBaseSize();
        contents.forEach(contentItem -> {
            // Web 编辑器变换
            String contentItemId = contentItem.get("uuid").asText();
            String originPosition = contentItem.get(CONTENT_KEY_POSITION).asText();

            ArVector3 newClientPosition = getClientUpdatePosition(request, contentItemId);
            ArVector3 newEditorPosition = ArVector3.create(newClientPosition.getX() * spaceBaseSize, newClientPosition.getY() * spaceBaseSize, newClientPosition.getZ() * spaceBaseSize);
            log.info("updateWebEditorPosition {} -> {}", originPosition, newEditorPosition);

            if (contentItem instanceof ObjectNode) {
                ((ObjectNode) contentItem).put(CONTENT_KEY_POSITION, newEditorPosition.toString());
            }
        });
    }

    private void updateUnityEditorPosition(WorksUpdatePositionRequest request, JsonNode contents, CommonArProtocol commonArProtocol) {
        contents.forEach(contentItem -> {
            // Unity 编辑器变换
            // TODO: 不同模式算法不同
            String contentItemId = contentItem.get("uuid").asText();

            ArVector3 newClientPosition = getClientUpdatePosition(request, contentItemId);

            // 逆运算
            Double newEditorPositionX;
            Double newEditorPositionY;
            Double newEditorPositionZ;
            CommonArImageIdentifyModel identifyModel = (CommonArImageIdentifyModel) commonArProtocol.getIdentifyModel();
            if (!contentItem.get("size").isEmpty()) {
                Double sizeWidth = contentItem.get("size").get("width").asDouble();
                AssertUtils.notNull(sizeWidth, ResultCodeEnum.CLIENT_UPDATE_POSITION_FAILED, "width参数不能为空");
                newEditorPositionX = newClientPosition.getX() * identifyModel.getPixelPerUnit() / sizeWidth * identifyModel.getUnitFactor();
                newEditorPositionY = newClientPosition.getY() * identifyModel.getPixelPerUnit() / sizeWidth * identifyModel.getUnitFactor();
                newEditorPositionZ = newClientPosition.getZ() * identifyModel.getPixelPerUnit() / sizeWidth * identifyModel.getUnitFactor();
            } else {
                newEditorPositionX = newClientPosition.getX() * identifyModel.getUnitFactor();
                newEditorPositionY = newClientPosition.getY() * identifyModel.getUnitFactor();
                newEditorPositionZ = newClientPosition.getZ() * identifyModel.getUnitFactor();
            }
            JsonNode originPosition = contentItem.get(CONTENT_KEY_POSITION);
            String oldEditorPositionX = originPosition.get("x").asText();
            String oldEditorPositionY = originPosition.get("y").asText();
            String oldEditorPositionZ = originPosition.get("z").asText();
            log.info("updateUnityEditorPosition {} {} {} -> {} {} {}", oldEditorPositionX, oldEditorPositionY, oldEditorPositionZ, newEditorPositionX, newEditorPositionY, newEditorPositionZ);

            AssertUtils.isTrue(contentItem.get("x") instanceof ObjectNode, ResultCodeEnum.AR_UPDATE_POSITION_FORMAT_ERROR);
            AssertUtils.isTrue(contentItem.get("y") instanceof ObjectNode, ResultCodeEnum.AR_UPDATE_POSITION_FORMAT_ERROR);
            AssertUtils.isTrue(contentItem.get("z") instanceof ObjectNode, ResultCodeEnum.AR_UPDATE_POSITION_FORMAT_ERROR);
            ObjectNode positionNode = (ObjectNode) originPosition;
            positionNode.put("x", newEditorPositionX.toString());
            positionNode.put("y", newEditorPositionX.toString());
            positionNode.put("z", newEditorPositionX.toString());
        });
    }

    private ArVector3 getClientUpdatePosition(WorksUpdatePositionRequest request, String contentItemId) {
        Optional<WorksItemPositionVO> modifyPosition = request.getPositions().stream()
                .filter(worksItemPositionVO -> StringUtils.equals(contentItemId, worksItemPositionVO.getId())).findFirst();

        AssertUtils.isTrue(modifyPosition.isPresent(), ResultCodeEnum.AR_UPDATE_TARGET_CONTENT_ITEM_NOT_FOUND);
        AssertUtils.isTrue(StringUtils.isNotEmpty(modifyPosition.get().getPosition()), ResultCodeEnum.AR_UPDATE_POSITION_IS_EMPTY);

        ArVector3 newClientPosition = ArVector3.from(modifyPosition.get().getPosition());
        AssertUtils.notNull(newClientPosition, ResultCodeEnum.AR_UPDATE_POSITION_FORMAT_ERROR);

        return newClientPosition;
    }
}
